<?php
namespace TennisTournament\Service;

use TennisTournament\Model\Group;
use TennisTournament\Model\Match;
use TennisTournament\Model\StageOfTournamentEnum;
use TennisTournament\Model\WinnerEnum;
use TennisTournament\Persistent\MatchDao;
use TennisTournament\Persistent\MatchDaoDoctrine;
use Zend\Json\Json;

class MatchServiceImpl implements MatchService {

    /** @var MatchDao */
    protected $matchDao;

    /** @var PlayerService */
    protected $playerService;

    /** @var GroupStatsService */
    protected $groupStatsService;

    /** @var GroupService */
    protected $groupService;

    /**
     * @param MatchDao $matchDao
     * @param PlayerService $playerService
     * @param GroupStatsService $groupStatsService
     * @param GroupService $groupService
     */
    function __construct(MatchDao $matchDao, PlayerService $playerService,
                GroupStatsService $groupStatsService, GroupService $groupService) {
        $this->matchDao = $matchDao;
        $this->playerService = $playerService;
        $this->groupStatsService = $groupStatsService;
        $this->groupService = $groupService;
    }

    /**
     * Get an {@link Match} entity by its {@link $id}
     * @param $id integer
     * @return Match if have an entity with such id or null otherwise
     * @throws \Exception
     */
    public function get($id) {
        $this->matchDao->startTransaction();
        try {
            $match = $this->matchDao->get($id);

            $this->matchDao->commitTransaction();

            return $match;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Get all {@link Match} entities
     * @return Array from all {@link Match}
     * @throws \Exception
     */
    public function fetchAll() {
        $this->matchDao->startTransaction();
        try {
            $matches = $this->matchDao->fetchAll();

            $this->matchDao->commitTransaction();

            return $matches;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Fetch all matches based on their state of tournament
     * @param $stageOfTournament string from {@link StageOfTournamentEnum}
     * @throws \Exception if $stageOfTournament is not one from {@link StageOfTournamentEnum}
     * @return array from all matches or null if doesn't exist such matches
     */
    public function fetchAllWithStageOfTournament($stageOfTournament) {
        $this->matchDao->startTransaction();
        try {
            $matches = $this->matchDao->fetchAllWithStageOfTournament($stageOfTournament);

            $this->matchDao->commitTransaction();
            return $matches;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Save an {@link Match} entity;
     * @param Match $match
     * @return void
     * @throws \Exception
     */
    public function save($match) {
        $this->matchDao->startTransaction();
        try {
            $this->matchDao->save($match);

            $this->matchDao->commitTransaction();
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Delete an specific entity;
     * @param $entity
     * @return void
     * @throws \Exception
     */
    public function delete($entity) {
        $this->matchDao->startTransaction();
        try {
            $this->matchDao->delete($entity);

            $this->matchDao->commitTransaction();
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Encode a Match object into a JSON object
     * @param Match $match
     * @return string
     */
    public function toJSON(Match $match) {
        return Json::encode(
            $match->toArray()
        );
    }

    /**
     * Create all matches which will be played for specific Group
     * @param Group $group
     * @return void
     */
    public function createMatchesForSpecificGroup(Group $group) {
        $numberPlayersInGroup = count($group->getStats());
        $collectionGroupStats = $group->getStats();

        for ($i = 0; $i < $numberPlayersInGroup; $i++) {
            if ($numberPlayersInGroup >= GroupService::MIN_PLAYER_IN_GROUP) {
                for ($j = $i+1; $j < $numberPlayersInGroup; $j++) {
                    $match = new Match($collectionGroupStats[$i]->getPlayer(), $collectionGroupStats[$j]->getPlayer());
                    $match->setStageOfTournament(StageOfTournamentEnum::GROUP_STAGE);
                    $this->save($match);
                }
            }
        }
    }

    /**
     * Update Match entity
     * @param Match $match
     * @return void
     */
    public function update(Match $match) {

        if ($match->getResults() != null) {
            $hostPlayer = $match->getPlayerHost();
            $guestPlayer = $match->getPlayerGuest();

            $this->playerService->updateStats($hostPlayer, $match->getResults(), WinnerEnum::HOST,
                $match->getWinner() == WinnerEnum::HOST ? true : false);

            $this->playerService->updateStats($guestPlayer, $match->getResults(), WinnerEnum::GUEST,
                $match->getWinner() == WinnerEnum::GUEST ? true : false);

            $match->setPlayerHost($hostPlayer);
            $match->setPlayerGuest($guestPlayer);

            if ($match->getStageOfTournament() == StageOfTournamentEnum::GROUP_STAGE) {
                $this->groupStatsService->updateStats($hostPlayer, $match->getResults(), WinnerEnum::HOST,
                    $match->getWinner() == WinnerEnum::HOST ? true : false);

                $this->groupStatsService->updateStats($guestPlayer, $match->getResults(), WinnerEnum::GUEST,
                    $match->getWinner() == WinnerEnum::GUEST ? true : false);
            }
        }

        $this->matchDao->startTransaction();
        try {
            $this->matchDao->update($match);

            $this->matchDao->commitTransaction();
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }

        /*
            Check whether this is last played match into current stage of tournament.
            If it is true, then create matches for next stage of tournament
        */

        if ($this->isPlayedAllMatchesInStageOfTournament($match->getStageOfTournament())) {
            $this->createMatchesForNextTournamentStage($match->getStageOfTournament());
        }
    }

    /**
     * Fetch all matches played in specific Groups
     * @param Group $group
     * @return array | null
     */
    public function getAllMatchesBy(Group $group) {
        $this->matchDao->startTransaction();
        try {
            $matches = $this->matchDao->getAllMatchesBy($group);

            $this->matchDao->commitTransaction();

            return $matches;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Retrieve all Matches who are played in the current date
     * @param \DateTime $date
     * @return array
     */
    public function getAllMatchesByDate(\DateTime $date) {
        $this->matchDao->startTransaction();
        try {
            $matches = $this->matchDao->getAllMatchesByDate($date);

            $this->matchDao->commitTransaction();

            return $matches;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }


    /**
     * Check whether all matches in specific stage of tournament are played yet or not.
     * @param $stageOfTournament string
     * @return boolean
     */
    public function isPlayedAllMatchesInStageOfTournament($stageOfTournament) {
        $this->matchDao->startTransaction();
        try {
            $isPlayedAllMatchInGroupStage = $this->matchDao->isPlayedAllMatchesInStageOfTournament($stageOfTournament);

            $this->matchDao->commitTransaction();

            return $isPlayedAllMatchInGroupStage;
        } catch(\Exception $ex) {
            $this->matchDao->rollbackTransaction();
            throw $ex;
        }
    }

    /**
     * Create Matches for next Tournament stage.
     * If we should create Matches from Group stage to the next
     * Tournament stage, we receive collection from players from which
     * which will be created new matches.
     * @param string $currentStageOfTournament
     * @return void
     */
    public function createMatchesForNextTournamentStage($currentStageOfTournament) {
        if (StageOfTournamentEnum::GROUP_STAGE == $currentStageOfTournament) {
            $collectionGroups = $this->groupService->getAllGroupsSorted();
            $players = null;

            switch (count($collectionGroups)) {
                case GroupService::ELIGIBLE_NUMBER_GROUPS_ONE:
                case GroupService::ELIGIBLE_NUMBER_GROUPS_TWO:
                case GroupService::ELIGIBLE_NUMBER_GROUPS_FOUR:
                case GroupService::ELIGIBLE_NUMBER_GROUPS_EIGHT:
                    $count = 0;
                    // Get only Ranked players for the next phase of the tournament
                    for ($j = 0; $j < count($collectionGroups); $j++) {
                        for ($i = 0; $i < GroupService::PLAYERS_CONTINUE_TOURNAMENT; $i++) {
                            $players[$count++] = $collectionGroups[$j]->getStats()[$i]->getPlayer();
                        }
                    }

                switch (count($players)) {
                    case GroupService::ELIGIBLE_NUMBER_GROUPS_ONE * MatchService::PLAYERS_IN_MATCH:
                        $this->createMatchesForFirstPlace($players);
                        break;
                    case GroupService::ELIGIBLE_NUMBER_GROUPS_TWO * MatchService::PLAYERS_IN_MATCH:
                        $newPlayerSorting = array();
                        for ($i = 0, $j = 0; $i < count($players) / 2; $i++, $j += 2) {
                            $newPlayerSorting[$j] = $players[$i];
                            if ($i + 1 == count($players) / 2) {
                                $newPlayerSorting[$j + 1] = $players[$i + 1];
                            } else {
                                $newPlayerSorting[$j + 1] = $players[$i + 2 + 1];
                            }
                        }
                        $this->createMatchesForSemiFinals($newPlayerSorting);
                        break;
                    case GroupService::ELIGIBLE_NUMBER_GROUPS_FOUR * MatchService::PLAYERS_IN_MATCH:
                        $newPlayerSorting = array();
                        for ($i = 0, $j = 0; $i < count($players) / 2; $i++, $j += 2) {
                            $newPlayerSorting[$j] = $players[$i];
                            if ($i + 1 == count($players) / 2) {
                                $newPlayerSorting[$j + 1] = $players[$i + 1];
                            } else {
                                $newPlayerSorting[$j + 1] = $players[$i + 4 + 1];
                            }
                        }
                        $this->createMatchesForQuarterFinals($newPlayerSorting);
                        break;
                    case GroupService::ELIGIBLE_NUMBER_GROUPS_EIGHT * MatchService::PLAYERS_IN_MATCH:
                        $newPlayerSorting = array();
                        for ($i = 0, $j = 0; $i < count($players) / 2; $i++, $j += 2) {
                            $newPlayerSorting[$j] = $players[$i];
                            if ($i + 1 == count($players) / 2) {
                                $newPlayerSorting[$j + 1] = $players[$i + 1];
                            } else {
                                $newPlayerSorting[$j + 1] = $players[$i + 8 + 1];
                            }
                        }
                        $this->createMatchesForSecondStage($newPlayerSorting);
                }
            }
        } else {
            $playersWins = array();
            $matches = $this->fetchAllWithStageOfTournament($currentStageOfTournament);

            for ($i = 0; $i < count($matches); $i++) {
                $playersWins[$i] = $matches[$i]->getWinner() == WinnerEnum::HOST ? $matches[$i]->getPlayerHost() : $matches[$i]->getPlayerGuest();
            }

            switch ($currentStageOfTournament) {
                case StageOfTournamentEnum::SECOND_STAGE:
                    $this->createMatchesForQuarterFinals($playersWins);
                    break;
                case StageOfTournamentEnum::QUARTERFINAL:
                    $this->createMatchesForSemiFinals($playersWins);
                    break;
                case StageOfTournamentEnum::SEMIFINAL:
                    $playersLos = array();
                    for ($i = 0; $i < count($matches); $i++) {
                        $playersLos[$i] = $matches[$i]->getWinner() == WinnerEnum::HOST ? $matches[$i]->getPlayerGuest() : $matches[$i]->getPlayerHost();
                    }

                    $this->createMatchesForThirdPlace($playersLos);
                    $this->createMatchesForFirstPlace($playersWins);
            }
        }
    }

    /**
     * Create matches for second stage
     * @param array $players
     */
    private function createMatchesForSecondStage(array& $players) {
        for ($i = 0; $i < count($players) - 1; $i += 2) {

            $match = new Match($players[$i], $players[$i + 1]);
            $match->setStageOfTournament(StageOfTournamentEnum::SECOND_STAGE);

            $this->save($match);
        }
    }

    /**
     * create matches for quarter finals
     * @param array $players
     */
    private function createMatchesForQuarterFinals(array& $players) {
        for ($i = 0; $i < count($players) - 1; $i += 2) {
            $match = new Match($players[$i], $players[$i + 1]);
            $match->setStageOfTournament(StageOfTournamentEnum::QUARTERFINAL);

            $this->save($match);
        }
    }

    /**
     * Create matches for semi finals
     * @param array $players
     */
    private function createMatchesForSemiFinals(array& $players) {
        for ($i = 0; $i < count($players) - 1; $i += 2) {
            $match = new Match($players[$i], $players[$i + 1]);
            $match->setStageOfTournament(StageOfTournamentEnum::SEMIFINAL);

            $this->save($match);
        }
    }

    /**
     * Create matches for third place
     * @param array $players
     */
    private function createMatchesForThirdPlace(array& $players) {
        $match = new Match($players[0], $players[1]);
        $match->setStageOfTournament(StageOfTournamentEnum::THIRD_PLACE);

        $this->save($match);
    }

    /**
     * Create matches for first place
     * @param array $players
     */
    private function createMatchesForFirstPlace(array& $players) {
        $match = new Match($players[0], $players[1]);
        $match->setStageOfTournament(StageOfTournamentEnum::FINAL_STAGE);

        $this->save($match);
    }

}